﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;

namespace xxpl1.Cache
{
    public class DiskCacheFactory
    {
        private static readonly DiskCache diskCache = new DiskCache();

        public static DiskCache GetDiskCache()
        {
            return diskCache;
        }
    }

    public class DiskCache : IDisposable
    {
        private readonly Dictionary<string, byte[]> memoryCache = new Dictionary<string, byte[]>();
        private readonly Dictionary<string, DateTime> expirationInfo = new Dictionary<string, DateTime>();
        private readonly Timer expirationTimer;
        public readonly string cachePath;

        public DiskCache()
        {
            cachePath = Path.Combine(Environment.CurrentDirectory, "Cache");
            if (!Directory.Exists(cachePath))
                Directory.CreateDirectory(cachePath);

            expirationTimer = new Timer(Expiration, this, 0, 120000);

            if (File.Exists(Path.Combine(cachePath, "index")))
            {
                using (FileStream fs = File.OpenRead(Path.Combine(cachePath, "index")))
                {
                    BinaryFormatter binaryFormatter = new BinaryFormatter();
                    expirationInfo = (Dictionary<string, DateTime>)binaryFormatter.Deserialize(fs);
                }
            }
        }

        public void Set(string key, byte[] content, DateTime expire)
        {
            key = CacheKey.MapCacheKey(key);

            memoryCache[key] = content;

            expirationInfo[key] = expire;

            File.WriteAllBytes(Path.Combine(cachePath, key), content);
        }

        public byte[] Get(string key)
        {
            key = CacheKey.MapCacheKey(key);

            if (memoryCache.ContainsKey(key))
            {
                return memoryCache[key];
            }

            if (expirationInfo.ContainsKey(key))
            {
                string path = Path.Combine(cachePath, key);
                if (File.Exists(path))
                {
                    byte[] response = File.ReadAllBytes(path);
                    memoryCache.Add(key, response);
                    return response;
                }
                expirationInfo.Remove(key);
            }
            return null;
        }

        private void Expiration(object state)
        {
            List<string> exp = (from pair in expirationInfo where pair.Value < DateTime.UtcNow select pair.Key).ToList();

            foreach (string key in exp)
            {
                expirationInfo.Remove(key);
                memoryCache.Remove(key);
                File.Delete(Path.Combine(cachePath, key));
            }
        }

        public void Dispose()
        {
            expirationTimer.Dispose();

            using (FileStream fs = File.OpenWrite(Path.Combine(cachePath, "index")))
            {
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(fs, expirationInfo);
            }
        }
    }
}
